package com.limit.service;

import com.limit.constant.RedisKeyConstant;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

/**
 * @title: SignUpService
 * @Author xu-xiang
 * @Date: 2024/4/7 14:36
 * @description:
 */
@Service
public class SignInService {

    @Resource(name = "stringRedisTemplate")
    private StringRedisTemplate redisTemplate;

    /**
     * 查询当前用户签到次数
     *
     * @param currentUserId 当前登录用户
     */
    public Object getSignInCount(String currentUserId) {
        int currentMonthSignUpCount = getConsecutiveSignInCountCurrentMonth(currentUserId);

        return null;
    }

    /**
     * 查询当前用户本月[连续签到]次数
     */
    public int getConsecutiveSignInCountCurrentMonth(String currentUserId) {
        LocalDate today = LocalDate.now();
        int year = today.getYear();
        int month = today.getMonthValue();
        int dayOfMonth = today.getDayOfMonth(); //今天是当月第N天
        int consecutiveSignInCount = 0;

        // 为什么执行结果返回的是list? 因为bitField有多种命令, 各种命令有不同的返回结果
        List<Long> longList = redisTemplate.opsForValue()
                .bitField(RedisKeyConstant.SIGN_IN_PREFIX + ":" + currentUserId + ":" + year + ":" + month,
                        BitFieldSubCommands.create()
                                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                                .valueAt(0));

        if (longList != null) {
            Long countBit = longList.get(0);  //签到的二进制位的10进制正数
            if (countBit == null || countBit == 0) {
                return 0;
            }

            // 读取二进制位,1为已签到,0为未签到,直到读到0时暂停
            while ((countBit & 1) == 1) {
                countBit >>= 1;
                consecutiveSignInCount++;
            }
        }

        return consecutiveSignInCount;
    }

    /**
     * 查询当前用户截止到今天,最大连续签到数
     */
    public int getConsecutiveSignInCountAllTime(String currentUserId) {
        LocalDate today = LocalDate.now();
        int dayOfMonth = today.getDayOfMonth(); //今天是当月第N天
        int currentMonthCount = getConsecutiveSignInCountCurrentMonth(currentUserId);

        // 当前月并没有全部签到
        if (dayOfMonth != currentMonthCount) {
            return currentMonthCount;
        }

        // 当前月全部签到,查询历史最大连续签到
        LocalDate lastMonth = today.minusMonths(1);
        int year = lastMonth.getYear();
        int month = lastMonth.getMonthValue();

        DefaultRedisScript<Long> luaScript = new DefaultRedisScript<>();
        luaScript.setResultType(Long.class);
        luaScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/signInCount.lua")));

        Long beforeCurrentMonthCount = redisTemplate.execute(luaScript,
                Arrays.asList(RedisKeyConstant.SIGN_IN_PREFIX, currentUserId, String.valueOf(year), String.valueOf(month))
        );

        return beforeCurrentMonthCount != null ? currentMonthCount + beforeCurrentMonthCount.intValue() : currentMonthCount;
    }

    /**
     * 查询当前用户本月[所有签到]次数
     */
    public int getAllSignInCountCurrentMonth(String currentUserId) {
        LocalDate today = LocalDate.now();
        int year = today.getYear();
        int month = today.getMonthValue();
        int dayOfMonth = today.getDayOfMonth(); //今天是当月第N天
        int currentMonthSignUpCount = 0;
        List<Long> longList = redisTemplate.opsForValue()
                .bitField(RedisKeyConstant.SIGN_IN_PREFIX + ":" + currentUserId + ":" + year + ":" + month,
                        BitFieldSubCommands.create()
                                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                                .valueAt(0));

        if (longList != null) {
            Long countBit = longList.get(0);  //签到的二进制位的10进制正数
            if (countBit == null || countBit == 0) {
                return 0;
            }

            for (int i = 0; i < dayOfMonth; i++) {
                if ((countBit & 1) == 1) {
                    currentMonthSignUpCount++;
                }
                countBit >>= 1;
            }
        }

        return currentMonthSignUpCount;
    }

    /**
     * 当天签到
     */
    public void signInToday(String currentUserId) {
        LocalDate today = LocalDate.now();
        int year = today.getYear();
        int month = today.getMonthValue();
        int dayOfMonth = today.getDayOfMonth(); //今天是当月第N天
        redisTemplate.opsForValue().setBit(
                RedisKeyConstant.SIGN_IN_PREFIX + ":" + currentUserId + ":" + year + ":" + month,
                dayOfMonth - 1,
                true);
    }

    /**
     * 批量补签
     */
    public void signInBatch(String currentUserId, List<LocalDate> dateList) {
        Assert.notEmpty(dateList, "dateList must not be empty");
        LocalDate today = LocalDate.now();

        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            @SuppressWarnings("unchecked")
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                for (LocalDate date : dateList) {
                    Assert.isTrue(!date.isAfter(today), "date must not after today"); //不准签今天以后的日期
                    int year = date.getYear();
                    int month = date.getMonthValue();
                    int dayOfMonth = date.getDayOfMonth();
                    operations.opsForValue().setBit(
                            (K) (RedisKeyConstant.SIGN_IN_PREFIX + ":" + currentUserId + ":" + year + ":" + month),
                            dayOfMonth - 1,
                            true);
                }
                return null;  //必须返回null,spring内部调用的redisCallback
            }
        });
    }

}
